/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#include "SpriteManager.h"

#include "OgreMesh.h"
#include "OgreHardwareBuffer.h"
#include "OgreRenderQueue.h"

#include "SceneObject.h"

namespace PQEngine
{
	Sprite2D::SpriteList Sprite2D::_list;
	SpriteManager::SpriteManager(SceneObject* sceneObject, unsigned char targetQueue, bool afterQueue)
		: _sceneObject(sceneObject),_targetQueue(targetQueue),_afterQueue(afterQueue)
	{
		assert(_sceneObject);
		_sceneManager=_sceneObject->getSceneManager();
		_hardwareBuffer.setNull();

		_sceneManager->addRenderQueueListener(this);
	} 

	SpriteManager::~SpriteManager()
	{
		if (!_hardwareBuffer.isNull()){
			destroyHardwareBuffer();
		}

	   _sceneManager->removeRenderQueueListener(this);
	}

	void SpriteManager::renderQueueStarted(unsigned char queueGroupId, const Ogre::String &invocation, bool &skipThisInvocation)
	{
		if (!_afterQueue && queueGroupId==_targetQueue){
			renderBuffer();
		}
	} 

	void SpriteManager::renderQueueEnded(unsigned char queueGroupId, const Ogre::String &invocation, bool &repeatThisInvocation)
	{ 
		if (_afterQueue && queueGroupId==_targetQueue){
			renderBuffer();
		}
	}


	void SpriteManager::renderBuffer()
	{
		Ogre::RenderSystem* rs=Ogre::Root::getSingleton().getRenderSystem();
		SpriteList::iterator currIter, endIter;

		VertexChunk chunk;
		std::list<VertexChunk> chunks;

		Ogre::uint newSize=_sprites.size()*6;
		if (newSize<MIN_HARD_BUF_SIZE){
			newSize=MIN_HARD_BUF_SIZE;
		}

		// grow hardware buffer if needed
		if (_hardwareBuffer.isNull() || _hardwareBuffer->getNumVertices()<newSize){
			if (!_hardwareBuffer.isNull()){
				destroyHardwareBuffer();
			}

			createHardwareBuffer(newSize);

		}

		if (_sprites.empty()) return;

		// write quads to the hardware buffer, and remember chunks
		float* buffer;
		float z=-1;

		buffer=(float*)_hardwareBuffer->lock(Ogre::HardwareBuffer::HBL_DISCARD);

		endIter=_sprites.end();
		currIter=_sprites.begin();

		chunk.texHandle=(*currIter)->texHandle;
		chunk.vertexCount=0;

		while (currIter!=endIter){
			Sprite2D* currSpr=(*currIter);
			// 1st point (left bottom)
			*buffer=currSpr->x1; buffer++;
			*buffer=currSpr->y2; buffer++;
			*buffer=z; buffer++;
			*buffer=currSpr->tx1; buffer++;
			*buffer=currSpr->ty2; buffer++;
			// 2st point (right top)
			*buffer=currSpr->x2; buffer++;
			*buffer=currSpr->y1; buffer++;
			*buffer=z; buffer++;
			*buffer=currSpr->tx2; buffer++;
			*buffer=currSpr->ty1; buffer++;
			// 3rd point (left top)
			*buffer=currSpr->x1; buffer++;
			*buffer=currSpr->y1; buffer++;
			*buffer=z; buffer++;
			*buffer=currSpr->tx1; buffer++;
			*buffer=currSpr->ty1; buffer++;

			// 4th point (left bottom)
			*buffer=currSpr->x1; buffer++;
			*buffer=currSpr->y2; buffer++;
			*buffer=z; buffer++;
			*buffer=currSpr->tx1; buffer++;
			*buffer=currSpr->ty2; buffer++;
			// 5th point (right bottom)
			*buffer=currSpr->x2; buffer++;
			*buffer=currSpr->y1; buffer++;
			*buffer=z; buffer++;
			*buffer=currSpr->tx2; buffer++;
			*buffer=currSpr->ty1; buffer++;
			// 6th point (right top)
			*buffer=currSpr->x2; buffer++;
			*buffer=currSpr->y2; buffer++;
			*buffer=z; buffer++;
			*buffer=currSpr->tx2; buffer++;
			*buffer=currSpr->ty2; buffer++;

			// remember this chunk
			chunk.vertexCount+=6;
			currIter++;
			if (currIter==endIter || chunk.texHandle!=currSpr->texHandle){
				chunks.push_back(chunk);
				if (currIter!=endIter){
					chunk.texHandle=currSpr->texHandle;
					chunk.vertexCount=0;
				}
			}
		} 

		_hardwareBuffer->unlock();


		// set up render setting
		prepareForRender();


		// do the real render!
		Ogre::TexturePtr tp;
		std::list<VertexChunk>::iterator currChunk, endChunk;

		endChunk=chunks.end();
		_renderOperation.vertexData->vertexStart=0;
		for (currChunk=chunks.begin(); currChunk!=endChunk; currChunk++){
			_renderOperation.vertexData->vertexCount=currChunk->vertexCount;
			tp=Ogre::TextureManager::getSingleton().getByHandle(currChunk->texHandle);
			rs->_setTexture(0, true, tp->getName());
			rs->_render(_renderOperation);
			_renderOperation.vertexData->vertexStart+=currChunk->vertexCount;
		}

		//_sprites.clear();//this made user need add sprite every frame
	} 

	void SpriteManager::prepareForRender()
	{ 
	   Ogre::LayerBlendModeEx colorBlendMode;
	   Ogre::LayerBlendModeEx alphaBlendMode;
	   Ogre::TextureUnitState::UVWAddressingMode uvwAddressMode;

	   Ogre::RenderSystem* rs=Ogre::Root::getSingleton().getRenderSystem();

	   colorBlendMode.blendType=Ogre::LBT_COLOUR;
	   colorBlendMode.source1=Ogre::LBS_TEXTURE;
	   colorBlendMode.operation=Ogre::LBX_SOURCE1;

	   alphaBlendMode.blendType=Ogre::LBT_ALPHA;
	   alphaBlendMode.source1=Ogre::LBS_TEXTURE;
	   alphaBlendMode.operation=Ogre::LBX_SOURCE1;

	   uvwAddressMode.u=Ogre::TextureUnitState::TAM_CLAMP;
	   uvwAddressMode.v=Ogre::TextureUnitState::TAM_CLAMP;
	   uvwAddressMode.w=Ogre::TextureUnitState::TAM_CLAMP;

	   rs->_setWorldMatrix(Ogre::Matrix4::IDENTITY);
	   rs->_setViewMatrix(Ogre::Matrix4::IDENTITY);
	   rs->_setProjectionMatrix(Ogre::Matrix4::IDENTITY);
	   rs->_setTextureMatrix(0, Ogre::Matrix4::IDENTITY);
	   rs->_setTextureCoordSet(0, 0);
	   rs->_setTextureCoordCalculation(0, Ogre::TEXCALC_NONE);
	   rs->_setTextureUnitFiltering(0, Ogre::FO_LINEAR, Ogre::FO_LINEAR, Ogre::FO_POINT);
	   rs->_setTextureBlendMode(0, colorBlendMode);
	   rs->_setTextureBlendMode(0, alphaBlendMode);
	   rs->_setTextureAddressingMode(0, uvwAddressMode);
	   rs->_disableTextureUnitsFrom(1);
	   rs->setLightingEnabled(false);
	   rs->_setFog(Ogre::FOG_NONE);
	   rs->_setCullingMode(Ogre::CULL_NONE);
	   rs->_setDepthBufferParams(false, false);
	   rs->_setColourBufferWriteEnabled(true, true, true, false);
	   rs->setShadingType(Ogre::SO_GOURAUD);
	   rs->_setPolygonMode(Ogre::PM_SOLID);
	   rs->unbindGpuProgram(Ogre::GPT_FRAGMENT_PROGRAM);
	   rs->unbindGpuProgram(Ogre::GPT_VERTEX_PROGRAM);
	   rs->_setSceneBlending(Ogre::SBF_SOURCE_ALPHA, Ogre::SBF_ONE_MINUS_SOURCE_ALPHA);
	   rs->_setAlphaRejectSettings(Ogre::CMPF_ALWAYS_PASS, 0, true);
	}  


	void SpriteManager::createHardwareBuffer(Ogre::uint size)
	{
	   Ogre::VertexDeclaration* vd;

	   _renderOperation.vertexData=new Ogre::VertexData;
	   _renderOperation.vertexData->vertexStart=0;

	   vd=_renderOperation.vertexData->vertexDeclaration;
	   vd->addElement(0, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
	   vd->addElement(0, Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3),
		  Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);

	   _hardwareBuffer=Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
		  vd->getVertexSize(0),
		  size,// buffer size
		  Ogre::HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY_DISCARDABLE,
		   false);// use shadow buffer? no

	   _renderOperation.vertexData->vertexBufferBinding->setBinding(0, _hardwareBuffer);

	   _renderOperation.operationType=Ogre::RenderOperation::OT_TRIANGLE_LIST;
	   _renderOperation.useIndexes=false;

	}

	void SpriteManager::destroyHardwareBuffer()
	{
		if(_renderOperation.vertexData){
			delete _renderOperation.vertexData;
			_renderOperation.vertexData=0;
		}
	   
	   _hardwareBuffer.setNull();
	}

	void SpriteManager::addSprite(Sprite2D* sprite)
	{
	   _sprites.push_back(sprite);
	}

}
